<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>this in js</title>
</head>
<body>
<script type="text/javascript">
    // 默认绑定
    // 情景 1
    console.log('普通情况', this) // window

    // 情景 2
    function foo() {
        console.log('普通函数体', this)
    }

    foo() // window

    // 情景 3
    // this 受函数使用的绑定, 隐式绑定
    function get() {
        console.log(this.a);
    }

    var obj = {
        a: 2,
        get
    }

    obj.get() // 2
    // 在 obj.get 调用下, get 中 this 指向的就是 obj 这个对象
    // 从而得出结论: 如果是谁调用的函数, 那么函数中的 this 就会指向谁


    // 情景 4
    var obj2 = {
        obj,
        a: 1
    }

    obj2.obj.get() // 2
    // 可以看到,尽管有二层调用, this 指向的还是最近一层中的 this


    // 情景 5
    // this 丢失的情况
    // 更换调用方式:
    var getIns = obj.get
    getIns() // undefined
    // 可以得出结论 使用 `.` (或者`[]`) 调用的才是具有效用的


    // 情景 6
    var o1 = {
        text: 'o1',
        fn: function () {
            return this.text
        }
    }
    var o3 = {
        text: 'o3',
        fn: function () {
            console.log('o3', this)
            var fn = o1.fn
            return fn()
        }
    }

    console.log('o3 丢失 this 的情况', o3.fn()) // undefined
    // 尽管 o1.fn 是在 o3 里面调用的, 但是 this 的指向还是丢失了
    // 需要记住的是函数被赋值, this 就会丢失

    // 至于为什么会丢失,这是他的原理: http://www.ruanyifeng.com/blog/2018/06/javascript-this.html


    // 显示绑定:
    // 显示绑定有很多方法: bind/call/apply
    // 情景 7
    // 套用情景 5 this 丢失的场景, 可以看到, 现在打印的是我们想要的值了
    var getIns = obj.get.bind(obj)
    getIns() // 2

    // 情景 8
    // 使用 call/apply
    // 这两个函数的作用是一样的,唯一不一样的点在于传参, 一个是接受一个数组,一个是多个参数
    // call(this,1,2,3)
    // apply(this, [1,2,3])

    // 情景 8:
    // 套用情况 6 的丢失场景:
    var o3 = {
        text: 'o3',
        fn: function () {
            var fn = o1.fn
            return fn.call(this)
        }
    }

    console.log('情景 8  o3 丢失 this 的情况修改', o3.fn()) // undefined

    // 当然 apply 接受参数是一个数组, 自然有他的优点:

    var array = ['a', 'b'];
    var elements = [0, 1, 2];
    array.push.apply(array, elements);
    console.log(array); // ["a", "b", 0, 1, 2]
    // 通过 apply 直接 push 了多个参数
    // 在 es6 之后 也快使用这种方式: array.push(...elements)


    // 情景 9:
    // class 中的函数, 这也属于一种 this 丢失的场景
    // 熟悉 react 的人应该就知道这个场景
    class Foo {
        name = "mike"

        say() {
            console.log(this.a)
        }

    }

    var FooIns = new Foo()
    FooIns.say() // undefined

    // new 绑定
    // 场景 10
    // 这里说的new 绑定和情景 9 是不一样, 其实说的是构造函数的绑定:
    // es5代码:
    function Foo2() {
        this.bar = "LiLz"
    }

    const Foo2Ins = new Foo2()
    console.log(Foo2Ins.bar) // LiLz
    // 在上面情景一中 this 指向的是 window, 在场景 10 中, 通过 new 成功将 this 绑定在了 Foo2Ins 上

    // 场景 11
    class Foo3 {
        constructor() {
            this.bar = 'lisa'
        }
    }

    var Foo3Ins = new Foo3()
    console.log(Foo3Ins.bar) // lisa
    // 从这里可以看到 new 的作用, 虽然改变指向只是他的一部分功能


    // 特殊情况
    // 严格模式
    // 场景 12
    function foo2() {
        'use strict'
        console.log('严格模式下的 this', this)
    }

    foo2()

    // 箭头函数和 this
    // 我们来看看箭头函数对应的 this 有什么区别吧
    // 场景 13, 即使在严格模式下 箭头函数中的 this 也不会是 undefined
    const bar = () => {
        'use strict'
        console.log('箭头函数', this)
    }
    bar() // windows

    // 场景 14
    const get2 = () => {
        console.log(this, this.a);
    }

    var obj3 = {
        a: 2,
        get2
    }

    obj3.get2() // window, undefined
    // 调用无法改变其指向, 再看 bind/apply/call


    // 场景 15
    obj3.get2.bind(obj3)() // 同场景 14
    // 场景 16
    obj3.get2.call(obj3) // 场景 14

    // 场景 17, 遇到 new 时, 可以看到  箭头函数,他是无法被作为构造函数的

    const  Foo4 = () => {
        this.bar = "LiLz"
    }

    // const Foo4Ins = new Foo4()
    // console.log(Foo4Ins.bar) // TypeError: Foo4 is not a constructor

    // 场景 18:

    class Foo5 {
        name = "mike"

        say = () => {
            console.log(this.name)
        }

    }

    var Foo5Ins = new Foo5()
    Foo5Ins.say() // mike


    // 至此可以得出 this 改变的权重:  箭头函数 = new  > bind/apply/call > 函数调用

</script>

</body>
</html>
